package com.atguigu.gulimall.ware.service.impl;

import com.alibaba.fastjson.TypeReference;
import com.atguigu.common.exception.NoStockException;
import com.atguigu.common.to.mq.OrderTo;
import com.atguigu.common.to.mq.StockDetailTo;
import com.atguigu.common.to.mq.StockLockedTo;
import com.atguigu.common.utils.R;
import com.atguigu.gulimall.ware.entity.WareOrderTaskDetailEntity;
import com.atguigu.gulimall.ware.entity.WareOrderTaskEntity;
import com.atguigu.gulimall.ware.feign.OrderFeignService;
import com.atguigu.gulimall.ware.feign.ProductFeignService;
import com.atguigu.gulimall.ware.service.PurchaseDetailService;
import com.atguigu.common.to.SkuHasStockTo;
import com.atguigu.gulimall.ware.service.WareOrderTaskDetailService;
import com.atguigu.gulimall.ware.service.WareOrderTaskService;
import com.atguigu.gulimall.ware.vo.LockStockResult;
import com.atguigu.gulimall.ware.vo.OrderItemVo;
import com.atguigu.gulimall.ware.vo.OrderVo;
import com.atguigu.gulimall.ware.vo.WareSkuLockVo;
import com.rabbitmq.client.Channel;
import lombok.Data;
import org.apache.commons.lang.StringUtils;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.atguigu.common.utils.PageUtils;
import com.atguigu.common.utils.Query;

import com.atguigu.gulimall.ware.dao.WareSkuDao;
import com.atguigu.gulimall.ware.entity.WareSkuEntity;
import com.atguigu.gulimall.ware.service.WareSkuService;
import org.springframework.transaction.annotation.Transactional;


//@RabbitListener(queues = "stock.release.stock.queue")
@Service("wareSkuService")
public class WareSkuServiceImpl extends ServiceImpl<WareSkuDao, WareSkuEntity> implements WareSkuService {

    @Autowired
    WareOrderTaskDetailService wareOrderTaskDetailService;

    @Autowired
    WareOrderTaskService wareOrderTaskService;

    @Autowired
    WareSkuDao wareSkuDao;

    @Autowired
    RabbitTemplate rabbitTemplate;

    @Autowired
    PurchaseDetailService purchaseDetailService;

    @Autowired
    ProductFeignService productFeignService;

    @Autowired
    OrderFeignService orderFeignService;

    /**
     * 监听队列，接受消息，解锁库存
     * 1、下订单成功，订单过期没有支付被系统自动取消、用户手动取消。就要回滚库存
     * <p>
     * 2、下订单成功，库存锁定成功，接下来的其他业务失败，导致订单回滚。就要回滚库存
     * <p>
     * 注意：只要解锁库存失败，一定要告诉RabbitMQ不要删除该消息（重新尝试解锁）
     * 手动ack
     *
     * @param to
     * @param message
     */
//    @RabbitHandler
    public void handleStockLockedRelease(StockLockedTo to, Message message, Channel channel) throws IOException {
        try {
            System.out.println("收到解锁库存的消息");
            Long id = to.getId();//库存工作单的id
            StockDetailTo datail = to.getDatail();//库存工作单详情
            Long datailId = datail.getId();
            //解锁库存
            //1、先查询DB中，是否有关于这个订单的库存工作单详情信息。
            WareOrderTaskDetailEntity entity = wareOrderTaskDetailService.getById(datailId);
            if (entity != null) {
                //2、有数据，再去order系统查询当前订单状态，在决定是否解锁库存
                //  订单状态： 已取消，解锁库存
                //             没取消，不解锁库存
                //2.1）、根据工作单id查询订单号orderSn
                WareOrderTaskEntity wareOrderTaskEntity = wareOrderTaskService.getById(id);
                String orderSn = wareOrderTaskEntity.getOrderSn();
                //2.2)、远程调用->订单服务，根据orderSn订单号查询订单状态
                //TODO 这里远程调用order订单服务，有一些问题。
                //在order服务里。我们写了一个拦截器，只放行所有已登录的请求。而Feign远程调用时会创建一个新请求来远程访问，该新请求里没有保存登录后的cookie状态。order服务认为没有登录。
                //所以：这里远程调用会有问题，order服务的自定义的拦截器拒绝访问。
                R r = orderFeignService.getOrderStatus(orderSn);
                if (r.getCode() == 0) {
                    OrderVo data = r.getData(new TypeReference<OrderVo>() {
                    });
                    //2.3）、判断订单状态。只用订单不存在、或者订单已取消才解锁
                    if (data == null || data.getStatus() == 4) {
                        //2.4）、实际解锁库存
                        UnLockStock(datail.getSkuId(), datail.getWareId(), datail.getSkuNum(), datailId);
                        //2.5）、解锁成功后，手动ack，通知RabbitMQ服务器删除该消息
                        channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
                    }
                } else {
                    //2.6）、远程调用失败，还没有消费该消息
                    //手动ack，拒收消息且将该消息重新放入队列（等待下一次处理）
                    channel.basicReject(message.getMessageProperties().getDeliveryTag(), true);
                }
            } else {
                //3、没有数据，代表库存锁定事务自己已经回滚了，无需解锁
                //手动ack，通知RabbitMQ服务器删除该消息
                channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
            }
        } catch (Exception ex) {
            //有异常
            //手动ack，通知RabbitMQ服务器删除该消息
            channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
        }

    }

    /**
     * 实际解锁库存
     */
    private void UnLockStock(Long skuId, Long wareId, Integer num, Long taskDetailId) {
        wareSkuDao.unLockStock(skuId, wareId, num);
        //解锁库存之后，更新修改库存工作单的状态
        WareOrderTaskDetailEntity entity = new WareOrderTaskDetailEntity();
        entity.setId(taskDetailId);
        entity.setLockStatus(2);
        wareOrderTaskDetailService.updateById(entity);

    }


    @Override
    public PageUtils queryPage(Map<String, Object> params) {

        QueryWrapper<WareSkuEntity> wrapper = new QueryWrapper<>();
        //  拼接条件
        //wareId: 123,//仓库id
        //   skuId: 123//商品id
        String wareId = (String) params.get("wareId");
        String skuId = (String) params.get("skuId");
        if (!StringUtils.isEmpty(wareId)) {
            wrapper.eq("ware_id", wareId);
        }
        if (!StringUtils.isEmpty(skuId)) {
            wrapper.eq("sku_id", skuId);
        }

        IPage<WareSkuEntity> page = this.page(
                new Query<WareSkuEntity>().getPage(params), wrapper
        );

        return new PageUtils(page);
    }

    /**
     * 给仓库中添加一件商品（新增商品 或者 库存+1）
     *
     * @param skuId
     * @param wareId
     * @param skuNum
     */
    @Override
    public void addStock(Long skuId, Long wareId, Integer skuNum) {

        String name = null;

        try {
            //远程调用->商品服务，根据skuId获取当前sku信息
            //如果失败不需要回滚事务
            //1、自己catch异常，没有抛出异常，则不会回滚事务
            //TODO 还可以使用什么办法，让异常出现而不回滚？
            R r = productFeignService.info(skuId);
            if (r.getCode() == 0) {
                Map<String, Object> map = (Map<String, Object>) r.get("skuInfo");
                name = (String) map.get("skuName");
            }
        } catch (Exception ex) {
        }

        //1、先判断 当前仓库中是否存在当前商品
        WareSkuEntity entity = this.getOne(new QueryWrapper<WareSkuEntity>().eq("sku_id", skuId).eq("ware_id", wareId));
        if (entity != null) {
            //1.1）、存在，则：库存+1
            Integer stock = entity.getStock();
            stock += skuNum;
            entity.setStock(stock);
            entity.setSkuName(name);
            this.updateById(entity);
        } else {
            //1.2）、不存在，则：新增商品
            entity = new WareSkuEntity();
            entity.setSkuId(skuId);
            entity.setWareId(wareId);
            entity.setStock(skuNum);
            entity.setSkuName(name);
            this.save(entity);
        }
    }

    //    @Override
//    public List<SkuHasStockTo> getSkuHasStock(List<Long> skuIds) {
//
//        List<SkuHasStockTo> vos = new ArrayList<>();
//        for (Long skuId : skuIds) {
//            SkuHasStockTo vo = new SkuHasStockTo();
//            vo.setSkuId(skuId);
//            Long stock = this.baseMapper.getSkuStock(skuId);
//            if (stock != null) {
//                vo.setHasStock(stock > 0);
//            } else {
//                vo.setHasStock(false);
//            }
//            vos.add(vo);
//        }
//        return vos;
//    }
    @Override
    public List<SkuHasStockTo> getSkuHasStock(List<Long> skuIds) {

        List<SkuHasStockTo> collect = skuIds.stream().map((skuId) -> {
            SkuHasStockTo vo = new SkuHasStockTo();
            //查询是否有库存
            Long count = baseMapper.getSkuStock(skuId);
            vo.setSkuId(skuId);
            vo.setHasStock(count == null ? false : true);

            return vo;
        }).collect(Collectors.toList());
        return collect;
    }

    /**
     * 锁定库存：为某个订单锁定库存
     *
     * @param vo
     * @return
     */
    @Transactional
    @Override
    public Boolean orderLockStock(WareSkuLockVo vo) {

        /**
         * 保存库存工作单的信息。
         * 方便追溯。
         */
        WareOrderTaskEntity taskEntity = new WareOrderTaskEntity();
        taskEntity.setOrderSn(vo.getOrderSn());
        wareOrderTaskService.save(taskEntity);

        //1、按照下单的收货地址，找到一个就近仓库，锁定库存。（没有这样做）
        //1、根据skuId，查询每个商品在哪个仓库有库存
        List<OrderItemVo> locks = vo.getLocks();
        List<SkuWareHasStock> collect = locks.stream().map(item -> {
            SkuWareHasStock stock = new SkuWareHasStock();
            stock.setSkuId(item.getSkuId());
            stock.setNum(item.getCount());
            //查询这个商品在哪里有库存
            List<Long> wareIds = wareSkuDao.listWareIdHasSkuStock(item.getSkuId());
            stock.setWareId(wareIds);
            return stock;
        }).collect(Collectors.toList());
        //2、锁定商品
        Boolean allLock = true;
        for (SkuWareHasStock hasStock : collect) {
            Boolean skuStocked = false;
            Long skuId = hasStock.getSkuId();
            Integer num = hasStock.getNum();
            List<Long> wareIds = hasStock.getWareId();
            if (wareIds == null && wareIds.size() == 0) {
                //没有任何仓库有库存
                throw new NoStockException(skuId);
            }
            for (Long wareId : wareIds) {
                //锁定库存
                Long count = wareSkuDao.lockSkuStock(skuId, wareId, num);
                if (count == 1) {
                    //锁定成功
                    skuStocked = true;
                    //保存库存工作单详情的信息，方便追溯
                    WareOrderTaskDetailEntity detailEntity = new WareOrderTaskDetailEntity(null, skuId, null, hasStock.getNum(), taskEntity.getId(), wareId, 1);
                    wareOrderTaskDetailService.save(detailEntity);
                    //给MQ发送消息，记录某商品的库存锁定成功
                    StockLockedTo stockLockedTo = new StockLockedTo();
                    stockLockedTo.setId(taskEntity.getId());
                    StockDetailTo detail = new StockDetailTo();
                    BeanUtils.copyProperties(detailEntity, detail);
                    stockLockedTo.setDatail(detail);
                    rabbitTemplate.convertAndSend("stock-event-exchange", "stock.locked", stockLockedTo);
                    break;
                } else {
                    //当前仓库锁定失败，重试下一个仓库
                }
            }
            if (skuStocked == false) {
                //当前商品所有仓库都没锁住
                //直接退出，抛异常，让事务回滚数据
                throw new NoStockException(skuId);
            }
        }

        //3、能运行到这里，就表示所有商品都锁定成功
        return true;
    }

    /**
     * 解锁库存①
     */
    @Override
    public void unlockStock(StockLockedTo to) {
        Long id = to.getId();//库存工作单的id
        StockDetailTo datail_old = to.getDatail();//旧的 库存工作单详情
        //查询最新 库存工作单详情
        WareOrderTaskDetailEntity datail = wareOrderTaskDetailService.getById(datail_old.getId());
        Long datailId = datail.getId();
        //判断是否需要库存
        //1、先查询DB中，是否有关于这个订单的库存工作单详情信息。
        WareOrderTaskDetailEntity entity = wareOrderTaskDetailService.getById(datailId);
        if (entity != null) {
            //2、有数据，再去order系统查询当前订单状态，再决定是否解锁库存
            //  订单状态： 已取消，解锁库存
            //             没取消，不解锁库存
            //2.1）、根据工作单id查询订单号orderSn
            WareOrderTaskEntity wareOrderTaskEntity = wareOrderTaskService.getById(id);
            String orderSn = wareOrderTaskEntity.getOrderSn();
            //2.2)、远程调用->订单服务，根据orderSn订单号查询订单状态
            //TODO 这里远程调用order订单服务，有一些问题。
            //在order服务里。我们写了一个拦截器，只放行所有已登录的请求。而Feign远程调用时会创建一个新请求来远程访问，该新请求里没有保存登录后的cookie状态。order服务认为没有登录。
            //所以：这里远程调用会有问题，order服务的自定义的拦截器拒绝访问。
            R r = orderFeignService.getOrderStatus(orderSn);
            if (r.getCode() == 0) {
                OrderVo data = r.getData(new TypeReference<OrderVo>() {
                });
                //2.3）、判断订单状态。
                if (data == null || data.getStatus() == 4) {
                    //只用订单不存在、或者订单已取消，且商品的库存状态为锁定状态的商品才解锁库存。
                    if (datail.getLockStatus() == 1) {
                        UnLockStock(datail.getSkuId(), datail.getWareId(), datail.getSkuNum(), datailId);
                    }
                }
            } else {
                throw new RuntimeException();
            }
        } else {
            //无需解锁
        }

    }

    /**
     * 解锁库存②，在订单被关闭后触发
     */
    @Override
    @Transactional
    public void unlockStock(OrderTo orderTo) {
        String orderSn = orderTo.getOrderSn();
        //我们这里拿到的是整个订单，所以要根据订单查询每一个商品的库存状态后，再根据具体的库存状态进行库存解锁
        //1、根据订单号OrderSn查询对应的库存工作单
        WareOrderTaskEntity task = wareOrderTaskService.getOne(new QueryWrapper<WareOrderTaskEntity>().eq("order_sn", orderSn));
        Long id = task.getId();
        //2、找到当前库存工作单下，所有没有解锁的商品，进行库存解锁
        List<WareOrderTaskDetailEntity> list = wareOrderTaskDetailService.list(new QueryWrapper<WareOrderTaskDetailEntity>().eq("task_id", id).eq("lock_status", 1));
        for (WareOrderTaskDetailEntity one : list) {
            //解锁库存
            UnLockStock(one.getSkuId(),one.getWareId(),one.getSkuNum(),one.getId());
        }
    }

    @Data
    class SkuWareHasStock {
        private Long skuId;
        private Integer num;
        private List<Long> wareId;
    }


}